package com.wmx.thymeleafapp.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wmx.thymeleafapp.listeners.RedisSubListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;

/**
 * 自定义 RedisTemplate 序列化方式
 * 配置主题订阅 - Redis 消息监听器绑定监听指定通道
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/5/21 16:13
 */
@Configuration
public class RedisConfig {

    /**
     * 自定义的消息订阅监听器，当收到阅订的消息时，会将消息交给这个类处理
     */
    @Resource
    private RedisSubListener redisSubListener;

    /**
     * 自定义 RedisTemplate 序列化方式
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //创建 RedisTemplate，key 和 value 都采用了 Object 类型
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        //绑定 RedisConnectionFactory
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //创建 Jackson2JsonRedisSerializer 序列方式，对象类型使用 Object 类型，
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //设置一下 jackJson 的 ObjectMapper 对象参数
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置 RedisTemplate 序列化规则。因为 key 通常是普通的字符串，所以使用 StringRedisSerializer 即可。
        // 而 value 是对象时，才需要使用序列化与反序列化
        // key 序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // value 序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash key 序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // hash value 序列化规则
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //属性设置后操作
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 配置主题订阅
     * RedisMessageListenerContainer - Redis 消息监听器绑定监听指定通道
     * 1、可以添加多个监听器，监听多个通道，只需要将消息监听器与订阅的通道/主题绑定即可。
     * 2、订阅的通道可以配置在全局配置文件中，也可以配置在数据库中，
     * <p>
     * addMessageListener(MessageListener listener, Collection<? extends Topic> topics)：将消息监听器与多个订阅的通道/主题绑定
     * addMessageListener(MessageListener listener, Topic topic)：将消息监听器与订阅的通道/主题绑定
     * </p>
     * 3、如果是{@link KeyExpirationEventMessageListener} key 过期事件，则无需通过 addMessageListener 订阅，
     * 只需要注入 RedisMessageListenerContainer 后，过期事件监听器就会自动监听。
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 设置连接工厂，RedisConnectionFactory 可以直接从容器中取，也可以从 RedisTemplate 中取
        container.setConnectionFactory(connectionFactory);

        // 如果是{@link KeyExpirationEventMessageListener} key 过期事件，则无需通过 addMessageListener 订阅，
        // 只需要注入 RedisMessageListenerContainer 后，过期事件监听器就会自动监听.

        // 订阅名称叫 memoryCache 的通道, 类似 Redis 中的 subscribe 命令
        container.addMessageListener(redisSubListener, new ChannelTopic("memoryCache"));
        // 订阅名称以 'basic-' 开头的全部通道, 类似 Redis 的 pSubscribe 命令
        container.addMessageListener(redisSubListener, new PatternTopic("basic-*"));
        return container;
    }

}